#include "TextureBlock.h"

#if defined(__APPLE__) || defined(MACOSX)
	   #include <OpenGL/OpenGL.h>
	   #include <OpenGL/glu.h>
#endif
#if defined(_WIN32)
	#include <GL/gl.h>
	#include <GL/glu.h>
#endif

#include "Defines.h"
namespace Y{


#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT
	#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
	#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
#endif

bool TextureBlock::compressTextures = true;

TextureBlock::TextureBlock (int pw, int ph, int gs):
	gapSize(gs)
{
	int po2_0 = 1 << 0;
	int po2_1 = 1 << 1;
	while ( true ){
		if ( pw == po2_1 ){ break; }
		else if ( pw > po2_0 && pw < po2_1 ){ pw = po2_1;break; }
		po2_0 = po2_0 << 1;
		po2_1 = po2_1 << 1;
	}
	po2_0 = 1 << 0;
	po2_1 = 1 << 1;
	while ( true ){
		if ( ph == po2_1 ){ break; }
		else if ( ph > po2_0 && ph < po2_1 ){ ph = po2_1;break; }
		po2_0 = po2_0 << 1;
		po2_1 = po2_1 << 1;
	}
	
	width = pw;
	height = ph;
	
	glGenTextures(1,&texID);
	glBindTexture(GL_TEXTURE_2D,texID);
	
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
//	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 4.0f);
	glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
	GLubyte *bb = new GLubyte[pw * ph * 4];
	for ( int i = 0; i < pw * ph * 4; ++i ){
		bb[i] = 0;
	}
//	glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,bb);
	if ( compressTextures ){
//		gluBuild2DMipmaps(GL_TEXTURE_2D,GL_COMPRESSED_RGBA,width,height,GL_RGBA,GL_UNSIGNED_BYTE,bb);
		gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGBA,width,height,GL_RGBA,GL_UNSIGNED_BYTE,bb);
	}
	else{
		gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGBA,width,height,GL_RGBA,GL_UNSIGNED_BYTE,bb);
	}
	delete[] bb;
	
	
	TexPartition tp;
	tp.first().x() = gapSize;
	tp.first().y() = gapSize;
	tp.second().x() = pw - gapSize;
	tp.second().y() = ph - gapSize;
	free_partitions.insert( std::pair< int,TexPartition >( std::max(pw,ph) , tp ) );
	
}



bool TextureBlock::addTexture (Texture*t){
	int tw = t->width;
	int th = t->height;
	
	bool added = false;
	
	for ( iteratorLoop2(free_partitions,rit) ){
		
		if ( rit->second.second().x() < tw || rit->second.second().y() < th ){continue;}	//if it doesn't fit, continue
		TexPartition& fp = rit->second;
		
		added = true;
		
		
		TexCoordBlock& nb = blocks[ t->yID ];
		nb.position.x() = (float)(fp.first().x()) / (float)width;
		nb.position.y() = (float)(fp.first().y()) / (float)height;
		
		nb.dimensions.x() = (float)(tw) / (float) width;
		nb.dimensions.y() = (float)(th) / (float) height;
		
		glBindTexture(GL_TEXTURE_2D,texID);
		int px = fp.first().x();
		int py = fp.first().y();
		int pw = tw;
		int ph = th;
		GLubyte *currentImage = new GLubyte[tw * th * 4];
		GLubyte *newImage;
		for ( int i = 0; i < tw * th * 4; ++i ){
			currentImage[i] = t->image_data[i];
		}
		for (int i = 0; ; ++i ){
			glTexSubImage2D(GL_TEXTURE_2D,i,px,py,pw,ph,GL_RGBA,GL_UNSIGNED_BYTE,currentImage);
			newImage = new GLubyte[(pw / 2) * (ph / 2) * 4];
			gluScaleImage(GL_RGBA,pw,ph,GL_UNSIGNED_BYTE,currentImage,pw / 2,ph / 2,GL_UNSIGNED_BYTE,newImage);
			delete[] currentImage;
			currentImage = newImage;
			
			if ( ! (pw > 1 || ph > 1) ){
				break;
			}
			
			px /= 2;
			py /= 2;
			pw /= 2;
			ph /= 2;
			pw = std::max(pw,1);
			ph = std::max(ph,1);
		}
		delete[] currentImage;
		
		if ( fp.second().x() == tw && fp.second().y() == th ){
			free_partitions.erase(rit);
			break;
		}
		else if ( fp.second().x() == tw ){
			TexPartition new_fp;
			new_fp.first().x() = fp.first().x();
			new_fp.first().y() = fp.first().y() + th + gapSize;
			
			new_fp.second().x() = fp.second().x();
			new_fp.second().y() = fp.second().y() - th - gapSize;
			
			free_partitions.erase(rit);
			free_partitions.insert( std::pair<int,TexPartition>( std::max(new_fp.second().x(), new_fp.second().y()), new_fp ) );
			break;
		}
		else if ( fp.second().y() == th ){
			TexPartition new_fp;
			new_fp.first().x() = fp.first().x() + tw + gapSize;
			new_fp.first().y() = fp.first().y();
			
			new_fp.second().x() = fp.second().x() - tw - gapSize;
			new_fp.second().y() = fp.second().y();
			
			free_partitions.erase(rit);
			free_partitions.insert( std::pair<int,TexPartition>( std::max(new_fp.second().x(), new_fp.second().y()), new_fp ) );
			break;
		}
		else{
			TexPartition new_fp[2];
			
			int s1 = (fp.second().x() -  tw) * (th);
			int s2 = (tw) * (fp.second().y() - th);
			
			if ( s1 > s2 ){
				new_fp[0].first().x() = fp.first().x() + tw + gapSize;
				new_fp[0].first().y() = fp.first().y();
				
				new_fp[0].second().x() = fp.second().x() - tw - gapSize;
				new_fp[0].second().y() = th;
				
				new_fp[1].first().x() = fp.first().x();
				new_fp[1].first().y() = fp.first().y() + th + gapSize;
				
				new_fp[1].second().x() = fp.second().x();
				new_fp[1].second().y() = fp.second().y() - th - gapSize;
			}
			else{
				new_fp[0].first().x() = fp.first().x();
				new_fp[0].first().y() = fp.first().y() + th + gapSize;
				
				new_fp[0].second().x() = tw;
				new_fp[0].second().y() = fp.second().y() - th - gapSize;
				
				new_fp[1].first().x() = fp.first().x() + tw + gapSize;
				new_fp[1].first().y() = fp.first().y();
				
				new_fp[1].second().x() = fp.second().x() - tw - gapSize;
				new_fp[1].second().y() = fp.second().y();
			}
			
			free_partitions.erase(rit);
			free_partitions.insert( std::pair<int,TexPartition>( std::max(new_fp[0].second().x(), new_fp[0].second().y()), new_fp[0] ) );
			free_partitions.insert( std::pair<int,TexPartition>( std::max(new_fp[1].second().x(), new_fp[1].second().y()), new_fp[1] ) );
			break;
		}
	}
	
	if ( ! added ){
		return false;
	}
	else{
		consolidatePartitions();
		return true;
	}
}

bool TextureBlock::addTextures ( std::vector< Texture* >& t ){
	std::multimap< int , Texture *,rsorter > map;
	for ( int i = 0; i < t.size(); ++i ){
		map.insert( std::pair<int,Texture*>( std::max(t[i]->width,t[i]->height),t[i] ) );
	}
	for ( iteratorLoop2(map,it) ){
		if ( ! addTexture(it->second) ){
			return false;
		}
		else{
			for ( iteratorLoop2(t,vecit) ){
				if ( *vecit == it->second ){
					t.erase( vecit );
					break;
				}
			}
		}
	}
	return true;
}

bool TextureBlock::removeTexture (Texture*t){

	TexCoordBlock tb = texCoords(t);
	typeof(blocks.find(t->yID)) findit = blocks.find(t->yID);
	if ( findit == blocks.end() ){	return false; }
	
	TexPartition new_fp;
	new_fp.first().x() = tb.position.x();
	new_fp.first().y() = tb.position.y();
	new_fp.second().x() = tb.dimensions.x();
	new_fp.second().y() = tb.dimensions.y();
	
	blocks.erase(findit);
	free_partitions.insert( std::pair<int,TexPartition>( std::max(new_fp.second().x(), new_fp.second().y()), new_fp ) );
	consolidatePartitions();
	return true;
}

void TextureBlock::clear(){
	blocks.clear();
	free_partitions.clear();
	
	TexPartition tp;
	tp.first().x() = 0;
	tp.first().y() = 0;
	tp.second().x() = width;
	tp.second().y() = height;
	free_partitions.insert( std::pair< int,TexPartition >( std::max(width,height) , tp ) );
}

void TextureBlock::consolidatePartitions (){

	bool restart = false;
	
	for ( iteratorLoop2( free_partitions,partit ) ){
		typeof(partit) innerpartit = partit;
		innerpartit++;
		for ( ; innerpartit != free_partitions.end(); ++innerpartit ){
			TexPartition & t0 = partit->second;
			TexPartition & t1 = innerpartit->second;
			
			if ( t0.first().x() == t1.first().x() && 
					( t0.first().y() + t0.second().y() + gapSize == t1.first().y() + 0 || t1.first().y() + t1.second().y() + gapSize == t0.first().y() + 0 ) && 
					t0.second().x() == t1.second().x()	)
			{
				TexPartition new_fp;
				new_fp.first().x() = t0.first().x();
				new_fp.first().y() = std::min( t0.first().y(), t1.first().y() );
				
				new_fp.second().x() = t0.second().x();
				new_fp.second().y() = t0.second().y() + t1.second().y() + gapSize;
				
				free_partitions.erase(partit);
				free_partitions.erase(innerpartit);
				free_partitions.insert( std::pair<int,TexPartition>( std::max(new_fp.second().x(), new_fp.second().y()), new_fp ) );
				
				restart = true;
				break;
			}
			if ( t0.first().y() == t1.first().y() && 
					( t0.first().x() + t0.second().x() + gapSize == t1.first().x() + 0 || t1.first().x() + t1.second().x() + gapSize == t0.first().x() + 0 ) && 
					t0.second().y() == t1.second().y()	)
			{
				TexPartition new_fp;
				new_fp.first().x() = std::min( t0.first().x(), t1.first().x() );
				new_fp.first().y() = t0.first().y();
				
				new_fp.second().x() = t0.second().x() + t1.second().x() + gapSize;
				new_fp.second().y() = t0.second().y();
				
				free_partitions.erase(partit);
				free_partitions.erase(innerpartit);
				free_partitions.insert( std::pair<int,TexPartition>( std::max(new_fp.second().x(), new_fp.second().y()), new_fp ) );
				
				restart = true;
				break;
			}
		}
		if ( restart ){ break; }
	}
	
	if ( restart ){ consolidatePartitions(); }
}

}
